A subroutine call stack is a reserved memory area in a processor system for storing a so-called “return” address, i.e. the address at which to continue the execution of a program when the execution of a subroutine ends. The term “stack” reflects a last in-first out (LIFO) management style of the memory area, which is particularly well suited for handling nested subroutines. Common terminology relative to the management of a stack will be used hereinafter, such as “pushing” on the stack and “popping” from the stack, with the understanding that the underlying operations are actually obtained by managing read and write pointers of the memory area.
An unexpected change of the call stack may cause, when the subroutine ends, a program execution jump to an arbitrary location, identified by the changed value in the stack. The change may be due to a fraud attempt where a fraudster seeks to divert execution to a piece of pirate code or to bypass security checks.
A modification of the stack may be achieved by a laser attack, i.e. directing a pulsed laser beam on the stack region of the memory, or on the data bus during a read transaction with the stack. The value conveyed on the bus may also be changed by generating pulses in the power supply voltage during the read transaction.
To better illustrate this problem, consider the following pseudo-code in C language:
void main(void){...func1( );...}void func1(void){...func2( );...return;}void func2(void){...func3( );...VerifyPIN( );...return;}void func3(void){...return;}void VerifyPIN(void){...return;}
FIG. 1 illustrates the execution of this pseudo-code. The content of the call stack is shown above each block illustrating the execution of a subroutine or function. It is assumed that the stack is filled from the bottom in this representation.
During its execution, function main( ) calls function func1( ); the corresponding return address @1 is pushed on the stack, while function func1( ) is executed. Function func1( ) calls a function func2( ); the corresponding return address @2 is pushed on the stack. Function func2( ) calls a function func3( ); a further return address @3 is pushed on the stack.
The end of execution of function func3( ) is identified by the execution of the dedicated “return” instruction. This instruction causes the address @3 to be popped from the stack and execution to continue from that address, in this case the continuation of the execution of function func2( ). Function func2( ) then calls a VerifyPIN( ) function, whose role is, for example, to verify the entry of a PIN code. The corresponding return address @4 is pushed on the stack.
When execution of the VerifyPIN( ) function ends, the address @4 is popped from the stack and execution continues from that address, in this case in function func2( ).
As the functions end their execution, the return addresses are popped from the stack, and execution finally returns to the address @1 within the main( ) function.
In FIG. 2, during the execution of function func3( ), a fraudster changes the last address of the stack @3. The fraudster manages to replace that address by @4, which is the return address of the VerifyPIN( ) function. Thus, when the execution of function func3( ) ends, execution continues at address @4. The VerifyPIN( ) function for verifying the entry of a PIN code is thereby bypassed.
The fraudster does not need to know the exact return address @4 of the function he wants to bypass. He may simply find, by trial and error, an arbitrary address beyond the return address, which does not cause an error following the lack of execution of the code portion located between the return address and the arbitrary address.
Various solutions are known to avoid this type of fraud. For example, U.S. Pat. No. 7,581,089 suggests the use of two redundant call stacks. Each return address is pushed on the two stacks when calling subroutines. At the end of the execution of a subroutine, the system checks that the return address is present in both stacks. Even if a fraudster can change the addresses in both stacks, the chances of success of such changes are random: it is particularly difficult to change both stacks simultaneously in the same way.
Although this solution is effective, it requires additional complexity that increases the circuit surface area and reduces its performance.